package org.springframework.security.taglibs.authz;

import java.io.IOException;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.context.ApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.context.support.SecurityWebApplicationContextUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.FrameworkServlet;


public abstract class AbstractAuthorizeTag {
    private String access;
    private String url;
    private String method = "GET";


    protected abstract ServletRequest getRequest();

    protected abstract ServletResponse getResponse();

    protected abstract ServletContext getServletContext();

    public boolean authorize() throws IOException {
        boolean isAuthorized;

        if (StringUtils.hasText(getAccess())) {
            isAuthorized = authorizeUsingAccessExpression();

        } else if (StringUtils.hasText(getUrl())) {
            isAuthorized = authorizeUsingUrlCheck();

        } else {
            isAuthorized = false;

        }

        return isAuthorized;
    }

    public boolean authorizeUsingAccessExpression() throws IOException {
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            return false;
        }

        SecurityExpressionHandler<FilterInvocation> handler = getExpressionHandler();

        Expression accessExpression;
        try {
            accessExpression = handler.getExpressionParser().parseExpression(getAccess());

        } catch (ParseException e) {
            IOException ioException = new IOException();
            ioException.initCause(e);
            throw ioException;
        }

        return ExpressionUtils.evaluateAsBoolean(accessExpression,
                createExpressionEvaluationContext(handler));
    }

    protected EvaluationContext createExpressionEvaluationContext(
            SecurityExpressionHandler<FilterInvocation> handler) {
        FilterInvocation f = new FilterInvocation(getRequest(), getResponse(),
                new FilterChain() {
                    public void doFilter(ServletRequest request, ServletResponse response)
                            throws IOException, ServletException {
                        throw new UnsupportedOperationException();
                    }
                });

        return handler.createEvaluationContext(SecurityContextHolder.getContext()
                .getAuthentication(), f);
    }

    public boolean authorizeUsingUrlCheck() throws IOException {
        String contextPath = ((HttpServletRequest) getRequest()).getContextPath();
        Authentication currentUser = SecurityContextHolder.getContext()
                .getAuthentication();
        return getPrivilegeEvaluator().isAllowed(contextPath, getUrl(), getMethod(),
                currentUser);
    }

    public String getAccess() {
        return access;
    }

    public void setAccess(String access) {
        this.access = access;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = (method != null) ? method.toUpperCase() : null;
    }

/*------------- Private helper methods  -----------------*/


    @SuppressWarnings({"unchecked", "rawtypes"})
    private SecurityExpressionHandler<FilterInvocation> getExpressionHandler()
            throws IOException {
        //ApplicationContext appContext = SecurityWebApplicationContextUtils.findRequiredWebApplicationContext(getServletContext());


        // 1.获取 ServletContext 对象
        ServletContext servletContext = getServletContext();
        // 2.拼接 SpringMVC 在 ServletContext 域中的属性名
        String attrName = FrameworkServlet.SERVLET_CONTEXT_PREFIX + "springDispatcherServlet";
        // 3.从 ServletContext 域中获取 IOC 容器对象
        ApplicationContext appContext = (ApplicationContext)
                servletContext.getAttribute(attrName);

        Map<String, SecurityExpressionHandler> handlers = appContext
                .getBeansOfType(SecurityExpressionHandler.class);

        for (SecurityExpressionHandler h : handlers.values()) {
            if (FilterInvocation.class.equals(GenericTypeResolver.resolveTypeArgument(
                    h.getClass(), SecurityExpressionHandler.class))) {
                return h;
            }
        }

        throw new IOException(
                "No visible WebSecurityExpressionHandler instance could be found in the application "
                        + "context. There must be at least one in order to support expressions in JSP 'authorize' tags.");
    }

    private WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() throws IOException {
        WebInvocationPrivilegeEvaluator privEvaluatorFromRequest = (WebInvocationPrivilegeEvaluator) getRequest()
                .getAttribute(WebAttributes.WEB_INVOCATION_PRIVILEGE_EVALUATOR_ATTRIBUTE);
        if (privEvaluatorFromRequest != null) {
            return privEvaluatorFromRequest;
        }

        ApplicationContext ctx = SecurityWebApplicationContextUtils.findRequiredWebApplicationContext(getServletContext());
        Map<String, WebInvocationPrivilegeEvaluator> wipes = ctx
                .getBeansOfType(WebInvocationPrivilegeEvaluator.class);

        if (wipes.size() == 0) {
            throw new IOException(
                    "No visible WebInvocationPrivilegeEvaluator instance could be found in the application "
                            + "context. There must be at least one in order to support the use of URL access checks in 'authorize' tags.");
        }

        return (WebInvocationPrivilegeEvaluator) wipes.values().toArray()[0];
    }
}
